/**
 * Copyright (c) 2010 Basil Shikin, BugStat Project
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package org.bugstat.gui;

import java.awt.Dimension;
import java.awt.Point;
import java.awt.Window;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.Date;
import java.util.Properties;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

import javax.swing.JComponent;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.JTextComponent;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * This class tracks various UI properties. 
 *
 * @author Basil Shikin
 *
 */
public class PropertyTracker
{
    private static Log log = LogFactory.getLog( PropertyTracker.class );
    
    private final static String PROPERTY_FILE_NAME = "ui_state.properties";

    // This executor is used for all save operations
    private final static Executor propertySaver;

    static
    {
        final ThreadFactory factory = new ThreadFactory() {
            public Thread newThread(Runnable r)
            {
                return new Thread(r, "property_writer");
            }
        };

        propertySaver = Executors.newSingleThreadExecutor( factory );
    }
    
    // Current properties
    private final static Properties properties;

    static
    {
        properties = new Properties();
        load();
    }
    
   
    public String get( final String key, final String defaultValue)
    {
        return getProperty(key, defaultValue);
    }
    
    public int get( final String key, final int defaultValue)
    {
        return getProperty(key, defaultValue);
    }
    
    public boolean get( final String key, final boolean defaultValue)
    {
        return getProperty(key, defaultValue);
    }

    public void put( final String key, final int value)
    {
        putProperty( key, value);

        save();
    }
    
    public void put(String  key, String value)
    {
        putProperty( key, value);

        save();        
    }
    
    public void put( final String key, final boolean value)
    {
        putProperty( key, value);
        
        save();
    }

    public Point get( final String key, final Point defaultValue)
    {
        final int x = getProperty( key + ".x", defaultValue.x);
        final int y = getProperty( key + ".y", defaultValue.y);

        return new Point(x, y);
    }

    public void put( final String key, final Point value)
    {
        putProperty( key + ".x", value.x);
        putProperty( key + ".y", value.y);

        save();
    }

    public Dimension get( final String key, final Dimension defaultValue)
    {
        final int width  = getProperty( key + ".width", defaultValue.width);
        final int height = getProperty( key + ".height", defaultValue.height);

        return new Dimension(width, height);
    }

    public void put( final String key, final Dimension value)
    {
        putProperty( key + ".width", value.width);
        putProperty( key + ".height", value.height);

        save();
    }

    public void trackWindow(final Window window, final String key)
    {
        window.addComponentListener(new ComponentListener() {
            public void componentResized(ComponentEvent evt)
            {
                if (window.isEnabled()) put(key, window.getSize());
            }

            public void componentMoved(ComponentEvent evt)
            {
                if (window.isEnabled()) put(key, window.getLocation());
            }

            public void componentShown(ComponentEvent evt)
            {
                if (window.isEnabled()) put(key, window.getSize());
            }

            public void componentHidden(ComponentEvent evt)
            {
                if (window.isEnabled()) put(key, window.getSize());
            }
        });
    }

    public void trackComponent(final JComponent component, final String key)
    {
        component.addComponentListener(new ComponentListener() {
            public void componentResized(ComponentEvent evt)
            {
                if (component.isEnabled()) put(key, component.getSize());
            }

            public void componentMoved(ComponentEvent evt)
            {
                if (component.isEnabled()) put(key, component.getLocation());
            }

            public void componentShown(ComponentEvent evt)
            {
                if (component.isEnabled()) put(key, component.getSize());
            }

            public void componentHidden(ComponentEvent evt)
            {
                if (component.isEnabled()) put(key, component.getSize());
            }

        });

    }

    public void trackTextComponent( final JTextComponent component, final String key )
    {
        component.getDocument().addDocumentListener( new DocumentListener() {
            @Override
            public void removeUpdate(DocumentEvent e)
            {
                if ( component.isEnabled() ) put( key, component.getText() );
            }
            
            @Override
            public void insertUpdate(DocumentEvent e)
            {
                if ( component.isEnabled() ) put( key, component.getText() );
            }
            
            @Override
            public void changedUpdate(DocumentEvent e)
            {
                if ( component.isEnabled() ) put( key, component.getText() );                
            }
        } );
    }
    protected void putProperty( final String key, final int value )
    {
        putProperty(key, String.valueOf( value ) );
    }
    
    protected void putProperty( final String key, final boolean value )
    {
        putProperty(key, String.valueOf( value ) );
    }

    protected void putProperty( final String key, final String value )
    {
        // Check input
        if (key == null)          throw new IllegalArgumentException("No key specified");
        
        synchronized ( properties )
        {
            properties.put( key, value );
        }
    }


    protected int getProperty( final String key, int defaultValue )
    {
        final String value = getPropery( key );

        if ( value != null && value.length() > 0)
        {
            return Integer.parseInt( value );
        }
        else
        {
            return defaultValue;
        }
    }
    
    protected boolean getProperty( final String key, boolean defaultValue )
    {
        final String value = getPropery( key );
        
        if ( value != null && value.length() > 0)
        {
            return Boolean.parseBoolean( value );
        }
        else
        {
            return defaultValue;
        }
    }

    protected String getProperty( final String key, String defaultValue )
    {
        final String value = getPropery( key );
        
        if ( value != null )
        {
            return value;
        }
        else
        {
            return defaultValue;
        }
    }
    
    protected String getPropery( final String key )
    {
        synchronized ( properties )
        {
            return properties.getProperty( key );
        }
    }

    protected static void save()
    {
        propertySaver.execute( new Runnable() {
            public void run()
            {
                Writer writer = null;
                try
                {
                    final File propertyFile = new File( ApplicationDataLocator.getApplicationDataFolder(), PROPERTY_FILE_NAME );
                    if ( !propertyFile.exists() ) propertyFile.createNewFile();
                    
                    writer = new FileWriter( propertyFile, false );
                    synchronized ( properties )
                    {
                        properties.store( writer, "Last updated: " + ( new Date() ));
                    }
                }
                catch ( IOException e )
                {
                    log.error("Unable to save properties to " + PROPERTY_FILE_NAME, e );
                }
                finally
                {
                    try
                    {
                        if ( writer != null ) writer.close();
                    }
                    catch (IOException e) {}
                }
            }
        } );
    }
    
    protected static void load()
    {
        Reader reader = null;
        try
        {
            final File propertyFile = new File( ApplicationDataLocator.getApplicationDataFolder(), PROPERTY_FILE_NAME );
            if ( propertyFile.exists() )
            {
                reader = new FileReader( propertyFile );
                
                synchronized (  properties )
                {
                    properties.load( reader );
                }
            }
        }
        catch ( IOException e )
        {
            log.error("Unable to read properties from " + PROPERTY_FILE_NAME, e );
        }
        finally
        {
            try
            {
                if ( reader != null ) reader.close();
            }
            catch (IOException e) {}
        }
    }
}
